package com.central.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.central.gateway.common.Constants;
import com.central.gateway.common.LogVo;
import com.central.gateway.common.RiskManageResponseResult;
import com.central.gateway.error.BusinessException;
import com.central.gateway.service.RiskManageHttpService;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 风控过滤平台
 * @author zouzhihui
 */
@Component
@Slf4j
public class RiskManageGatewayFilterFactory extends AbstractGatewayFilterFactory<RiskManageGatewayFilterFactory.Config> {

    private static final String UTF_8 = "utf-8";

    private static final String WILDCARD = "**";

    private static final String API = "/api";

    @Autowired
    private RiskManageHttpService riskManageHttpService;

    public RiskManageGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest serverHttpRequest = exchange.getRequest();
            String method = serverHttpRequest.getMethodValue();
            String value = serverHttpRequest.getPath().value();
            String host = serverHttpRequest.getHeaders().getFirst("Host");
            if (HttpMethod.GET.toString().equals(method)) {
                RiskManageResponseResult riskManageResponseResult = null;
                try {
                    if (config.getUrl().contains(WILDCARD)) {
                        String s;
                        if (value.contains(API) && !Objects.equals(host, config.getRobotHost())) {
                            s = config.getUrl().substring(0, config.getUrl().indexOf(WILDCARD)) + value.substring(5);
                            riskManageResponseResult = riskManageHttpService.doGet(s, serverHttpRequest.getQueryParams(), exchange.getRequest().getHeaders());
                        } else {
                            s = config.getUrl().substring(0, config.getUrl().indexOf(WILDCARD)) + value.substring(1);
                            riskManageResponseResult = riskManageHttpService.doGet(s, serverHttpRequest.getQueryParams(), exchange.getRequest().getHeaders());
                        }
                    } else {
                        riskManageResponseResult = riskManageHttpService.doGet(config.getUrl(), serverHttpRequest.getQueryParams(), exchange.getRequest().getHeaders());
                    }
                    setRiskManageResponseResult(exchange, JSON.toJSONString(riskManageResponseResult));
                } catch (BusinessException e) {
                    LogVo logVo = setRiskManageResponseResult(exchange, e.toString());
                    if (Objects.nonNull(logVo)) {
                        log.info(logVo.toString());
                        exchange.getAttributes().remove(Constants.LOG_VO_ATTR);
                    }
                    throw e;
                } catch (Exception e) {
                    setRiskManageResponseResult(exchange, e.toString());
                    log.error(e.getMessage(), e);
                }
                if (!Boolean.TRUE.equals(config.getDown())) {
                    if (riskManageResponseResult == null) {
                       throw new BusinessException("160000", "系统异常");
                    }
                    return doResponse(exchange, riskManageResponseResult);
                }
            } else if (HttpMethod.POST.toString().equals(method)) {
                return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    String bodyString = null;
                    try {
                        bodyString = new String(bytes, UTF_8);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    DataBufferUtils.release(dataBuffer);
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        DataBuffer buffer = exchange.getResponse().bufferFactory()
                                .wrap(bytes);
                        return Mono.just(buffer);
                    });
                    RiskManageResponseResult riskManageResponseResult = null;
                    try {
                        if (config.getUrl().contains(WILDCARD)) {
                            String s;
                            if (value.contains(API)) {
                                s = config.getUrl().substring(0, config.getUrl().indexOf(WILDCARD)) + value.substring(5);
                                riskManageResponseResult = riskManageHttpService.doPost(s, bodyString, exchange.getRequest().getHeaders());
                            } else {
                                s = config.getUrl().substring(0, config.getUrl().indexOf(WILDCARD)) + value.substring(1);
                                riskManageResponseResult = riskManageHttpService.doPost(s, bodyString, exchange.getRequest().getHeaders());
                            }
                        } else {
                            riskManageResponseResult = riskManageHttpService.doPost(config.getUrl(), bodyString, exchange.getRequest().getHeaders());
                        }
                        setRiskManageResponseResult(exchange, JSON.toJSONString(riskManageResponseResult));
                    } catch (BusinessException e) {
                        LogVo logVo = setRiskManageResponseResult(exchange, e.toString());
                        if (Objects.nonNull(logVo)) {
                            log.info(logVo.toString());
                            exchange.getAttributes().remove(Constants.LOG_VO_ATTR);
                        }
                        throw e;
                    } catch (Exception e) {
                        setRiskManageResponseResult(exchange, e.toString());
                        log.error(e.getMessage(), e);
                    }
                    if (!Boolean.TRUE.equals(config.getDown())) {
                        if (riskManageResponseResult == null) {
                            throw new BusinessException("160000", "系统异常");
                        }
                        return doResponse(exchange, riskManageResponseResult);
                    }
                    ServerHttpRequest mutatedRequest;
                    if (Boolean.TRUE.equals(config.getUpdateParameter()) && Objects.nonNull(riskManageResponseResult)) {
                        bodyString = updateParameter(bodyString, riskManageResponseResult);
                        setRequestParameter(exchange, bodyString);
                        DataBuffer bodyFlux = stringBuffer(bodyString);
                        mutatedRequest = getRequest(exchange.getRequest(), 0, bodyFlux);
                    } else {
                        mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                    }

                    return chain.filter(exchange.mutate().request(mutatedRequest)
                            .build());
                });
            }
            return chain.filter(exchange);
        };
    }

    private LogVo setRiskManageResponseResult(ServerWebExchange exchange, String riskManageResponseResult) {
        Object attribute = exchange.getAttribute(Constants.LOG_VO_ATTR);
        if (Objects.nonNull(attribute)) {
            LogVo logVo = (LogVo) attribute;
            logVo.setEndTime(System.currentTimeMillis());
            logVo.setRiskManageResponseResult(riskManageResponseResult);
            return logVo;
        }
        return null;
    }

    private LogVo setRequestParameter(ServerWebExchange exchange, String bodyString) {
        Object attribute = exchange.getAttribute(Constants.LOG_VO_ATTR);
        if (Objects.nonNull(attribute)) {
            LogVo logVo = (LogVo) attribute;
            logVo.setRequestParameter(bodyString);
            return logVo;
        }
        return null;
    }

    private String updateParameter(String bodyString, RiskManageResponseResult riskManageResponseResult) {
        JSONObject jsonObject = JSONObject.parseObject(bodyString);

        Object data = riskManageResponseResult.getData();
        if (data instanceof Map) {
            data = ((Map) data).get("data");
            if (data instanceof Map) {
                updateObjectParameter(jsonObject, (Map) data);
            }
        }

        return jsonObject.toJSONString();
    }

    private void updateObjectParameter(JSONObject jsonObject, Map<String, Object> parameter) {
        for (String key : parameter.keySet()) {
            for (String key1 : jsonObject.keySet()) {
                if (key.toLowerCase().equals(key1.toLowerCase())) {
                    Object o = parameter.get(key);
                    if (o instanceof Map) {
                        updateObjectParameter(jsonObject.getJSONObject(key1), (Map) o);
                    } else if (o instanceof List) {
                        updateListParameter(jsonObject.getJSONArray(key1), (List) o);
                    } else {
                        jsonObject.put(key1, o);
                    }
                }
            }
        }
    }

    private void updateListParameter(JSONArray jsonArray, List<Object> parameter) {
        Object o;
        for (int i = 0; i < parameter.size(); i++) {
            o = parameter.get(i);
            if (o instanceof Map) {
                updateObjectParameter(jsonArray.getJSONObject(i), (Map) o);
            } else if (o instanceof List) {
                updateListParameter(jsonArray.getJSONArray(i), (List) o);
            } else {
                jsonArray.set(i, o);
            }
        }
    }

    private ServerHttpRequest getRequest(ServerHttpRequest request, int contentLength, DataBuffer bodyFlux) {
        return new ServerHttpRequestDecorator(request) {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                if (contentLength > 0) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    // TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return Flux.just(bodyFlux);
            }
        };
    }

    private DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }

    private Mono<Void> doResponse(ServerWebExchange exchange, RiskManageResponseResult riskManageResponseResult) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        DataBuffer wrap = response.bufferFactory().wrap(JSONObject.toJSONString(riskManageResponseResult).getBytes(StandardCharsets.UTF_8));
        return exchange.getResponse().writeWith(Mono.just(wrap));
    }

    public static class Config {
        private String url;

        private Boolean down;

        private Boolean updateParameter;

        private String robotHost;

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public Boolean getDown() {
            return down;
        }

        public void setDown(Boolean down) {
            this.down = down;
        }

        public Boolean getUpdateParameter() {
            return updateParameter;
        }

        public void setUpdateParameter(Boolean updateParameter) {
            this.updateParameter = updateParameter;
        }

        public String getRobotHost() {
            return robotHost;
        }

        public void setRobotHost(String robotHost) {
            this.robotHost = robotHost;
        }
    }
}
